<?php
defined('DS') or define('DS', DIRECTORY_SEPARATOR);

require(APP_DIR . '/vendor/autoload.php');

spl_autoload_register(function ($className) {
    $fileName = '';
    $fileName .= str_replace('_', DS, $className) . '.php';
    $fullFileName = APP_DIR . DS . $fileName;
    $fullFileName = str_replace('\\', '/', $fullFileName);
    if (file_exists($fullFileName)) {
        try {
            require($fullFileName);
            logs($fullFileName . " is load ok!");
        } catch (Exception $e) {
            err("error: " . $e->getMessage());
        }
    }
});

set_error_handler(function ($errno, $errstr, $errfile, $errline) {
    if (0 === error_reporting() || 30711 === error_reporting()) return false;
    $msg = "ERROR";
    if ($errno == E_WARNING) $msg = "WARNING";
    if ($errno == E_NOTICE) $msg = "NOTICE";
    if ($errno == E_STRICT) $msg = "STRICT";
    if ($errno == 8192) $msg = "DEPRECATED";
    err("$msg: $errstr in $errfile on line $errline");
});

function dump($var, $exit = false)
{
    $output = print_r($var, true);
    if (!$GLOBALS['debug']) return error_log(str_replace("\n", '', $output));
    $html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><div align=left><pre>" . htmlspecialchars($output) . "</pre></div></body></html>";
    if (!empty($GLOBALS['out_handler'])) {
        call_user_func($GLOBALS['out_handler'], $html);
    } else {
        echo $html;
        if ($exit) exit();
    }
}

/**
 * @param $model_name
 * @return Model|NoneClass
 */
function M($model_name)
{
    global $app;
    $app_module = $app->app_module;
    if (substr($model_name, 0, 1) == '/') {
        $arr = explode('/', $model_name);
        $app_module = $arr[1];
        $model_name = $arr[2];
    }
    if (substr($model_name, strlen($model_name) - 7, 7) == 'Control') {
        $_controller_name = "app" . "\\" . $app_module . "\\" . 'controller' . "\\" . $model_name;
        if (!class_exists($_controller_name, true)) {
            return new NoneClass("Err: Controller '$_controller_name'");
        } else {
            return new $_controller_name($app);
        }
    } else {
        $_model_name = "app" . "\\" . $app_module . "\\" . 'model' . "\\" . $model_name;
        if (!class_exists($_model_name, true)) {
            return new NoneClass("Err: Model '$_model_name'");
        } else {
            return new $_model_name($app);
        }
    }
}

function err($msg)
{
    $msg = htmlspecialchars($msg);
    $traces = debug_backtrace();
    if (!empty($GLOBALS['err_handler'])) {
        call_user_func($GLOBALS['err_handler'], $msg, $traces);
    } else {
        if (!$GLOBALS['debug']) {
            error_log($msg);
        } else {
            if (ob_get_contents()) ob_end_clean();
            $error = "";
            foreach ($traces as $trace) {
                if (is_array($trace) && !empty($trace["file"])) {
                    $souceline = _err_getSource($trace["file"], $trace["line"]);
                    if ($souceline) {
                        $error .= "<ul><li><span>" . $trace["file"] . " on line " . $trace["line"] . " </span></li></ul>";
                        $error .= "<div id=\"oneborder\">";
                        foreach ($souceline as $singleline) $error .= $singleline;
                        $error .= "</div>";
                    }
                }
            }
            $html = <<<HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="robots" content="noindex, nofollow, noarchive"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{$msg}</title>
<style>body{padding:0;margin:0;word-wrap:break-word;word-break:break-all;font-family:Courier,Arial,sans-serif;background:#ebf8ff;color:#5e5e5e}
div,h2,p,span{margin:0;padding:0}ul{margin:0;padding:0;list-style-type:none;font-size:0;line-height:0}
#body{width:918px;margin:0 auto}#main{width:918px;margin:13px auto 0 auto;padding:0 0 35px 0}
#contents{width:918px;float:left;margin:13px auto 0 auto;background:#FFF;padding:8px 0 0 9px}
#contents h2{display:block;background:#cff0f3;font-weight:bold;font-size:20px;padding:12px 0 12px 30px;margin:0 10px 22px 1px}
#contents ul{padding:0 0 0 18px;font-size:0;line-height:0}#contents ul li{display:block;padding:0;color:#8f8f8f;background-color:inherit;font:normal 14px Arial,Helvetica,sans-serif;margin:0}
#contents ul li span{display:block;color:#408baa;background-color:inherit;font:bold 14px Arial,Helvetica,sans-serif;padding:0 0 10px 0;margin:0}
#oneborder{width:800px;font:normal 14px Arial,Helvetica,sans-serif;border:#ebf3f5 solid 4px;margin:0 30px 20px 30px;padding:10px 20px;line-height:23px}
#oneborder span{padding:0;margin:0}#oneborder #current{background:#cff0f3}</style>
</head>
<body>
<div id="main"><div id="contents"><h2>{$msg}</h2>{$error}</div></div><div style="clear:both;padding-bottom:50px;"/>
</body>
</html>
HTML;
            if (!empty($GLOBALS['out_handler'])) {
                return call_user_func($GLOBALS['out_handler'], $html);
            } else {
                echo $html;
            }
        }
    }
}

function _err_highlight_code($code)
{
    if (preg_match('/\<\?(php)?[^[:graph:]]/i', $code)) {
        return highlight_string($code, TRUE);
    } else {
        return preg_replace('/(&lt;\?php&nbsp;)+/i', "", highlight_string("<?php " . $code, TRUE));
    }
}

function _err_getSource($file, $line)
{
    if (!(file_exists($file) && is_file($file))) {
        return '';
    }
    $data = file($file);
    $count = count($data) - 1;
    $start = $line - 5;
    if ($start < 1) {
        $start = 1;
    }
    $end = $line + 5;
    if ($end > $count) {
        $end = $count + 1;
    }
    $returns = array();
    for ($i = $start; $i <= $end; $i++) {
        if ($i == $line) {
            $returns[] = "<div id='current'>" . $i . ".&nbsp;" . _err_highlight_code($data[$i - 1], TRUE) . "</div>";
        } else {
            $returns[] = $i . ".&nbsp;" . _err_highlight_code($data[$i - 1], TRUE);
        }
    }
    return $returns;

}

function is_available_className($name)
{
    return preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $name);
}

function isCli()
{
    return preg_match("/cli/i", php_sapi_name()) ? true : false;
}

function logs($log, $isFile = false)
{
    $traces = debug_backtrace();
    if (!empty($traces[1]['file'])) {
        $content = "[" . date('y-m-d h:i:s', time()) . "]\t==={$traces[1]['file']}::{$traces[1]['function']}({$traces[1]['line']})===\n"
            . print_r($log, true) . "\n";
        if ($isFile) {
            $file = APP_DIR . "/logs/success.log";
            file_put_contents($file, $content, FILE_APPEND);
        } else {
            if (isCli()) {
                print_r($content);
            }
        }
    } else {
        $content = "[" . date('y-m-d h:i:s', time()) . "]\t==={$traces[0]['file']}::{$traces[0]['function']}({$traces[0]['line']})===\n"
            . print_r($log, true) . "\n";
        if (isCli()) {
            print_r($content);
        }
    }
}

function isAjax()
{
    global $app;
    return isset($app->header['x-requested-with']) && $app->header['x-requested-with'] == 'XMLHttpRequest' || isset($app->header['X-REQUESTED-WITH']) && $app->header['X-REQUESTED-WITH'] == 'XMLHttpRequest' || isset($app->header['HTTP_X_REQUESTED_WITH']) && $app->header['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
}

function time2Str($time)
{
    if ($time == '') return '';
    $time = time() - $time;
    if ($time < 60) {
        return '刚刚';
    } elseif ($time < 60 * 60) {
        $min = floor($time / 60);
        return $min . '分钟前';
    } elseif ($time < 60 * 60 * 24) {
        $h = floor($time / (60 * 60));
        return $h . '小时前';
    } elseif ($time < 60 * 60 * 24 * 3) {
        $d = floor($time / (60 * 60 * 24));
        $hTime = date("H:i", $time);
        if ($d == 1)
            return '昨天 ' . $hTime;
        else
            return '前天 ' . $hTime;
    } else {
        return date("Y-m-d", $time);
    }
}

if (!function_exists('apache_request_headers')) {
    function apache_request_headers()
    {
        $arh = array();
        $rx_http = '/\AHTTP_/';
        foreach ($_SERVER as $key => $val) {
            if (preg_match($rx_http, $key)) {
                $arh_key = preg_replace($rx_http, '', $key);
                $rx_matches = explode('_', $arh_key);
                if (count($rx_matches) > 0 and strlen($arh_key) > 2) {
                    foreach ($rx_matches as $ak_key => $ak_val) $rx_matches[$ak_key] = ucfirst($ak_val);
                    $arh_key = implode('-', $rx_matches);
                }
                $arh[$arh_key] = $val;
            }
        }
        return ($arh);
    }
}

/***
 * 返回以原数组某个值为下标的新数据
 *
 * @param array $array
 * @param string $key
 * @param int $type 1一维数组 2二维数组
 * @return array
 */
function array_under_reset($array, $key, $type = 1)
{
    if (is_array($array)) {
        $tmp = array();
        foreach ($array as $v) {
            if ($type === 1) {
                $tmp[$v[$key]] = $v;
            } elseif ($type === 2) {
                $tmp[$v[$key]][] = $v;
            }
        }
        return $tmp;
    } else {
        return $array;
    }
}

/**
 * 过滤list不要的字段
 *
 * @param array $List 查询记录集
 * @param array $fieldArr 需要显示字段
 * @return mixed
 */
function filterListField($List, $fieldArr)
{
    foreach ($List as $index => $item) {
        foreach ($item as $key => $field) {
            if (!in_array($key, $fieldArr)) unset($List[$index][$key]);
        }
    }
    return $List;
}

/**
 * 过滤array不要的字段
 *
 * @param array $item 查询记录集
 * @param array $fieldArr 需要显示字段
 * @return mixed
 */
function filterField($item, $fieldArr)
{
    foreach ($item as $key => $field) {
        if (!in_array($key, $fieldArr)) unset($item[$key]);
    }
    return $item;
}

/***
 * 一维数组转换多个单一对象
 *
 * @param $array
 * @return array
 */
function array_index_reset($array)
{
    if (is_array($array)) {
        $tmp = array();
        foreach ($array as $i => $v) {
            $tmp[] = [$i => $v];
        }
        return $tmp;
    } else {
        return $array;
    }
}

class NoneClass
{
    private static $classTitle = "";

    public function __construct($title = null)
    {
        self::$classTitle = $title;
    }

    public function __call($name, $arguments)
    {
        throw new Exception(self::$classTitle . " Unable to call method '$name' " . implode(', ', $arguments));
    }

    public static function __callStatic($name, $arguments)
    {
        throw new Exception(self::$classTitle . " Unable to call static method '$name' " . implode(', ', $arguments));
    }
}

class Core
{
    public $table;
    public $header = null;
    public $server = null;
    public $request = null;
    public $cookie = null;
    public $files = null;
    public $app_module = null;
    public $app_controller = null;
    public $app_action = null;
    public $method = null;
    public $response = null;

    function __construct($_app = null)
    {
    }

    function ok($data = [], $total = 0, $msg = "操作完成")
    {
        if (is_string($data)) {
            $ret = array('res' => 0, 'msg' => $data, 'data' => [], 'items' => []);
        } elseif (is_array($data) && count($data) == count($data, 1)) {
            $ret = array('res' => 1, 'data' => $data, 'total' => $total);
        } else {
            $ret = array('res' => 1, 'items' => $data, 'total' => $total);
        }
        if (isset($data['items'])) {
            unset($ret['data']);
            foreach ($data as $key => $val) {
                $ret[$key] = $val;
            }
        }
        if (isset($msg)) {
            $ret['msg'] = $msg;
        }
        $this->exit(json_encode($ret));
    }

    function error($str = '系统错误')
    {
        $ret = array('res' => 0, 'msg' => print_r($str, true));
        $this->exit(json_encode($ret));
    }

    function end($ret)
    {
        $this->ok($ret);
    }

    function exit($ret = '')
    {
        if (APP_EVN == 'nginx') {
            return die($ret);
        }
        if (APP_EVN == 'swoole') {
            global $app;
            $app->response->write($ret);
            return $app->response->end();
        }
        if (APP_EVN == 'workerman') {
            global $app;
            return $app->response->send($ret);
        }
    }

    function ioAll($ret = [])
    {
        global $app;
        $app->request += $ret;
        return $app->request;
    }

    function ioSet($key, $val = null)
    {
        global $app;
        if ($val === null) {
            parse_str($key, $para);
            $app->ioAll($para);
            return count($para);
        }
        $app->request[$key] = $val;
        return $val;
    }

    function ioMust($key, $msg = '')
    {
        global $app;
        $value = $app->ioGet($key);
        if ($value === "") {
            $msg = $msg ?: "必须参数:" . $key;
            $app->ok($msg);
        }
        return $value;
    }

    function ioGet($key, $def = '')
    {
        global $app;
        if (isset($app->request[$key]) && ($app->request[$key] === 0 || $app->request[$key] === '0')) return 0;
        return (!isset($app->request[$key]) || $app->request[$key] == '') ? $def : $app->request[$key];
    }

    function write($str = '')
    {
        echo $str;
    }

    function header($key, $value = '')
    {
        if ($value == '')
            header($key);
        else
            header($key . ": " . $value);
    }

    function status($code)
    {
        header("HTTP/1.1 $code");
    }

    function detach()
    {
        return true;
    }

    public static function appName()
    {
        global $app;
        return $app->app_module;
    }

    /**
     * curl请求
     *
     * @param $url
     * @param string $data
     * @param bool $post
     * @param int $timeOut
     * @return mixed
     */
    function curlPost($url, $data = '', $post = true, $timeOut = 5)
    {
        $ch = @curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeOut * 2);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeOut);
        if ($post) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        $result = @curl_exec($ch);
        if ($result == FALSE) {
            err(curl_error($ch));
        }
        curl_close($ch);
        return $result;
    }
}

class Controller extends Core
{
    public $layout;
    public $_auto_display = true;
    protected $_v;
    private $_data = array();

    function init()
    {
    }

    function __construct()
    {
        $this->init();
    }

    function &__get($name)
    {
        return $this->_data[$name];
    }

    function __set($name, $value)
    {
        $this->_data[$name] = $value;
    }

    function display($tpl_name, $return = false)
    {
        if (!$this->_v) {
            $compile_dir = isset($GLOBALS['view']['compile_dir']) ? $GLOBALS['view']['compile_dir'] : APP_DIR . DS . 'protected' . DS . 'tmp';
            $this->_v = new View(APP_DIR . DS . 'protected' . DS . 'view', $compile_dir);
        }
        $this->_v->assign(get_object_vars($this));
        $this->_v->assign($this->_data);
        if ($this->layout) {
            $this->_v->assign('__template_file', $tpl_name);
            $tpl_name = $this->layout;
        }
        $this->_auto_display = false;

        if ($return) {
            return $this->_v->render($tpl_name);
        } else {
            echo $this->_v->render($tpl_name);
        }
    }
}

class Top extends Core
{
    function __construct($table_name = null)
    {
        if ($table_name) $this->table = $table_name;
    }

    function findAll($conditions = array(), $sort = null, $fields = '*', $limit = null)
    {
        $sort = !empty($sort) ? ' ORDER BY ' . $sort : '';
        $conditions = $this->_where($conditions);

        $sql = ' FROM ' . $this->table . $conditions["_where"];
        if (is_array($limit)) {
            $total = $this->query('SELECT COUNT(*) as M_COUNTER ' . $sql, $conditions["_bindParams"]);
            if (!isset($total[0]['M_COUNTER']) || $total[0]['M_COUNTER'] == 0) return array();

            $limit = $limit + array(1, 10, 10);
            $limit = $this->pager($limit[0], $limit[1], $limit[2], $total[0]['M_COUNTER']);
            $limit = empty($limit) ? '' : ' LIMIT ' . $limit['offset'] . ',' . $limit['limit'];
        } else {
            $limit = !empty($limit) ? ' LIMIT ' . $limit : '';
        }
        return $this->query('SELECT ' . $fields . $sql . $sort . $limit, $conditions["_bindParams"]);
    }

    function find($conditions = array(), $sort = null, $fields = '*')
    {
        $res = $this->findAll($conditions, $sort, $fields, 1);
        return !empty($res) ? array_pop($res) : false;
    }

    function update($conditions, $row)
    {
        $values = array();
        foreach ($row as $k => $v) {
            $values[":M_UPDATE_" . $k] = $v;
            $setstr[] = "`{$k}` = " . ":M_UPDATE_" . $k;
        }
        $conditions = $this->_where($conditions);
        return $this->execute("UPDATE " . $this->table . " SET " . implode(', ', $setstr) . $conditions["_where"], $conditions["_bindParams"] + $values);
    }

    function incr($conditions, $field, $optval = 1)
    {
        $conditions = $this->_where($conditions);
        return $this->execute("UPDATE " . $this->table . " SET `{$field}` = `{$field}` + :M_INCR_VAL " . $conditions["_where"], $conditions["_bindParams"] + array(":M_INCR_VAL" => $optval));
    }

    function decr($conditions, $field, $optval = 1)
    {
        return $this->incr($conditions, $field, -$optval);
    }

    function delete($conditions)
    {
        $conditions = $this->_where($conditions);
        return $this->execute("DELETE FROM " . $this->table . $conditions["_where"], $conditions["_bindParams"]);
    }

    function create($row)
    {
        $values = array();
        foreach ($row as $k => $v) {
            $keys[] = "`{$k}`";
            $values[":" . $k] = $v;
            $marks[] = ":" . $k;
        }
        $this->execute("INSERT INTO " . $this->table . " (" . implode(', ', $keys) . ") VALUES (" . implode(', ', $marks) . ")", $values);
        return $this->dbInstance($GLOBALS['mysql'], 'master')->lastInsertId();
    }

    function findCount($conditions)
    {
        $conditions = $this->_where($conditions);
        $count = $this->query("SELECT COUNT(*) AS M_COUNTER FROM " . $this->table . $conditions["_where"], $conditions["_bindParams"]);
        return isset($count[0]['M_COUNTER']) && $count[0]['M_COUNTER'] ? $count[0]['M_COUNTER'] : 0;
    }

    function dumpSql()
    {
        return $this->sql;
    }

    function pager($page, $pageSize = 10, $scope = 10, $total)
    {
        $this->page = null;
        if ($total > $pageSize) {
            $total_page = ceil($total / $pageSize);
            $page = min(intval(max($page, 1)), $total_page);
            $this->page = array(
                'total_count' => $total,
                'page_size' => $pageSize,
                'total_page' => $total_page,
                'first_page' => 1,
                'prev_page' => ((1 == $page) ? 1 : ($page - 1)),
                'next_page' => (($page == $total_page) ? $total_page : ($page + 1)),
                'last_page' => $total_page,
                'current_page' => $page,
                'all_pages' => array(),
                'offset' => ($page - 1) * $pageSize,
                'limit' => $pageSize,
            );
            $scope = (int)$scope;
            if ($total_page <= $scope) {
                $this->page['all_pages'] = range(1, $total_page);
            } elseif ($page <= $scope / 2) {
                $this->page['all_pages'] = range(1, $scope);
            } elseif ($page <= $total_page - $scope / 2) {
                $right = $page + (int)($scope / 2);
                $this->page['all_pages'] = range($right - $scope + 1, $right);
            } else {
                $this->page['all_pages'] = range($total_page - $scope + 1, $total_page);
            }
        }
        return $this->page;
    }

    function query($sql, $params = array())
    {
        return $this->execute($sql, $params, true);
    }

    function execute($sql, $params = array(), $readonly = false)
    {
        $this->sql[] = $sql;
        try {
            if ($readonly && !empty($GLOBALS['mysql']['MYSQL_SLAVE'])) {
                $slave_key = array_rand($GLOBALS['mysql']['MYSQL_SLAVE']);
                $sth = $this->dbInstance($GLOBALS['mysql']['MYSQL_SLAVE'][$slave_key], 'slave_' . $slave_key)->prepare($sql);
            } else {
                $sth = $this->dbInstance($GLOBALS['mysql'], 'master')->prepare($sql);
            }

            if (is_array($params) && !empty($params)) {
                foreach ($params as $k => &$v) {
                    if (is_int($v)) {
                        $data_type = PDO::PARAM_INT;
                    } elseif (is_bool($v)) {
                        $data_type = PDO::PARAM_BOOL;
                    } elseif (is_null($v)) {
                        $data_type = PDO::PARAM_NULL;
                    } else {
                        $data_type = PDO::PARAM_STR;
                    }
                    $sth->bindParam($k, $v, $data_type);
                }
            }

            if ($sth->execute()) return $readonly ? $sth->fetchAll(PDO::FETCH_ASSOC) : $sth->rowCount();
            $err = $sth->errorInfo();
            err('Database SQL: "' . $sql . '", ErrorInfo: ' . $err[2], 1);
        } catch (PDOException $e) {

        }
    }

    /**
     * @param $db_config
     * @param $db_config_key
     * @param bool $force_replace
     * @return PDO
     */
    function dbInstance($db_config, $db_config_key, $force_replace = false)
    {
        if ($force_replace || empty($GLOBALS['mysql_instances'][$db_config_key])) {
            try {
                if (!class_exists("PDO") || !in_array("mysql", PDO::getAvailableDrivers(), true)) {
                    err('Database Err: PDO or PDO_MYSQL doesn\'t exist!');
                }
                $GLOBALS['mysql_instances'][$db_config_key] = new PDO("mysql:host={$db_config['MYSQL_HOST']};dbname={$db_config['MYSQL_DB']};port={$db_config['MYSQL_PORT']}", $db_config['MYSQL_USER'], $db_config['MYSQL_PASS'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'' . $db_config['MYSQL_CHARSET'] . '\''));
            } catch (PDOException $e) {
                err('Database Err: ' . $e->getMessage());
            }
        } else {
            try {
                if (strpos($GLOBALS['mysql_instances'][$db_config_key]->getAttribute(PDO::ATTR_SERVER_INFO), 'Questions') == false) {
                    logs("mysql_instances is relink!");
                    return $this->dbInstance($db_config, $db_config_key, true);
                }
            } catch (PDOException $e) {
                logs("mysql_instances is error relink!");
                return $this->dbInstance($db_config, $db_config_key, true);
            }
        }
        return $GLOBALS['mysql_instances'][$db_config_key];
    }

    private function _where($conditions)
    {
        $result = array("_where" => " ", "_bindParams" => array());
        if (is_array($conditions) && !empty($conditions)) {
            $fieldss = array();
            $sql = null;
            $join = array();
            if (isset($conditions[0]) && $sql = $conditions[0]) unset($conditions[0]);
            foreach ($conditions as $key => $condition) {
                if (substr($key, 0, 1) != ":") {
                    unset($conditions[$key]);
                    $conditions[":" . $key] = $condition;
                }
                $join[] = "`{$key}` = :{$key}";
            }
            if (!$sql) $sql = join(" AND ", $join);

            $result["_where"] = " WHERE " . $sql;
            $result["_bindParams"] = $conditions;
        }
        return $result;
    }
}

class Model extends Top
{
    public $page;
    public $sql = array();
    public $db = 'mysql';
    public $key = 'id';
    public $idxGroupCol = '';
    public $clos = null;
    public $sort = null;
    public $conditions = [];
    public $bindConditionsState = false;
    public $bindConditions = [];
    public $joinTable = [];
    public $dicts = [];

    function BeforeUpdate(&$v, &$m = null)
    {
        return true;
    }

    function BeforeEdit(&$v, &$m = null)
    {
        return true;
    }

    function AfterUpdate($v, $o = [])
    {
        return true;
    }

    function BeforeDelete(&$v, &$m = '')
    {
        return false;
    }

    function AfterDelete($v)
    {
        return true;
    }

    function BeforeQuery(&$v, &$r)
    {
        return true;
    }

    function AfterQuery(&$v = array())
    {
        return true;
    }

    function GetTable()
    {
        return $this->table;
    }

    function SetWhere(&$v = array())
    {
        return true;
    }

    function FormatRow(&$v = array())
    {
        return true;
    }

    function setJoinRow(&$v = array())
    {
        foreach ($this->joinTable as $col => $className) {
            $cols = explode(':', $col);
            $col = $cols[0];
            $wkey = (count($cols) == 2) ? $cols[1] : 'id';
            $this->joinRow($v, $className, $col, $wkey);
        }
    }

    function joinRow(&$row, $className, $col, $wKey = 'id')
    {
        $arr = explode(',', $row[$col]);
        foreach ($arr as $id) {
            if ($id == '')
                continue;
            $data = M($className)->getOneX($wKey, $id);
            if (!$data)
                continue;
            foreach ($data as $key => $val) {
                if (!isset($row[$col . '_' . $key])) {
                    $row[$col . '_' . $key] = $val;
                } else {
                    $row[$col . '_' . $key] .= ',' . $val;
                }
            }
        }
    }

    function _doDric(&$row)
    {
        foreach ($this->dicts as $key => $v) {
            if (empty($row[$key])) {
                $row[$key . '_str'] = '';
            } else if (isset($v[$row[$key]])) {
                $row[$key . '_str'] = $v[$row[$key]];
            } else {
                $row[$key . '_str'] = $row[$key];
            }
        }
    }

    /**
     * 捆绑时间类的条件@$isTime 是否要转时间戳
     *
     * @param $v
     * @param $col
     * @param bool $isTime
     * @return Model
     */
    function bindDate(&$v, $col, $isTime = true)
    {
        if ($isTime) {
            if (!empty($v[$col . "1"])) {
                $v[$col . "1"] = self::DateToTime($v[$col . "1"]);
            }
            if (!empty($v[$col . "2"])) {
                if (strlen($v[$col . "1"]) == 10)
                    $v[$col . "2"] = self::DateToTime(substr($v[$col . "2"], 0, 10) . " 23:59:59");
                else {
                    $v[$col . "2"] = self::DateToTime($v[$col . "2"]);
                }
            }
        } else {
            if (!empty($v[$col . "1"])) {
                if (strlen($v[$col . "1"]) == 10) $v[$col . "1"] = substr($v[$col . "1"], 0, 10) . " 00:00:00";
            }
            if (!empty($v[$col . "2"])) {
                if (strlen($v[$col . "2"]) == 10) $v[$col . "2"] = substr($v[$col . "2"], 0, 10) . " 23:59:59";
            }
        }
        $this->bind($v, $col . "1", '>=', $col);
        $this->bind($v, $col . "2", '<=', $col);
        return $this;
    }

    /**
     * 数组，值下标，符号，字段列名 都支持逗号批量格式
     *
     * @param $v
     * @param $box
     * @param string $flag
     * @param string $col
     * @return Model|void
     */
    function bind(&$v, $box, $flag = '=', $col = '')
    {
        if ($box == 'keyword' && $flag == 'like') {
            if (isset($v[$box])) {
                $val = $v[$box];
                $val = trim($val);
                $val = str_replace('　', ',', $val);
                $val = str_replace('，', ',', $val);
                $val = str_replace(' ', ',', $val);
                $val = str_replace(',,', ',', $val);
                $arr = explode(',', $val);
                foreach ($arr as $words) {
                    if ($words) {
                        $this->bindConditions[$box] = "$words like :$words";
                    }
                }
            }
            return;
        }
        if (isset($v[$box])) {
            $val = $v[$box];
            if ($val === 'desc') {
                $this->bindConditions['desc'] = "$box desc";
                return;
            } elseif ($val === 'asc') {
                $this->bindConditions['asc'] = "$box asc";
                return;
            } elseif ($val === 'null') {
                $this->bindConditions[$box] = "$box = ':$box' or $box is null";
                return;
            }
            if ($flag == 'inset' && !empty($v[$box])) {
                $this->bindConditions[$box] = "find_in_set(:inset)";
                return;
            }
            if ($flag == 'in' || @strstr($v[$box], ',')) {
                $data = $v[$box];
                $arr = explode(',', $data);
                $temp = array();
                foreach ($arr as $key => $val) {
                    $temp[] = $val;
                }
                $col = ($col) ? $col : $box;
                $this->bindConditions[$box] = "{$col} in ( :inId )";
                return;
            }
        }
        $boxArr = explode(',', $box);
        foreach ($boxArr as $ipt) {
            if (!isset($v[$ipt]) || $v[$ipt] === '') {
                continue;
            }
            if ($col == '') {
                $this->bindConditions[$box] = "$ipt $flag :$ipt";
            } else {
                $cols = explode(',', $col);
                $temp = array();
                foreach ($cols as $kc) {
                    $temp[] = "$kc $flag :$box ";
                }
                $this->bindConditions[$box] = "( " . implode(' or ', $temp) . " )";
            }
        }
        return $this;
    }

    function whereRaw($sql, $arr = array())
    {
        $this->conditions[] = [$sql, $arr];
        return $this;
    }

    function orWhere($col, $flag, $val = null)
    {
        if ($val == null) {
            $val = $flag;
            $flag = '=';
        }
        if ($flag == 'like') {
            $val = '%' . $val . '%';
        }
        $this->conditions[] = [" or  $col $flag ? ", $val];
        return $this;
    }

    function getAll($conditions = array(), $format = false, $fields = '*')
    {
        return $this->getList($conditions, $format, $fields);
    }

    function getList($conditions = array(), $format = true, $fields = '*')
    {
        if (empty($conditions)) $conditions = $this->ioAll();
        if ($this->bindConditionsState == false) {
            $this->SetWhere($conditions);
            $this->bindConditionsState = true;
        }
        $this->conditions = $conditions;
        $_deleteId = $this->ioGet('_deleteId');
        if ($_deleteId) {
            $_deleteIds = explode(",", $_deleteId);
            foreach ($_deleteIds as $deleteId) {
                $this->delete([$this->key => $deleteId]);
            }
        }
        $_upId = $this->ioGet('_upId');
        if ($_upId) {
            $this->ResetIdx($_upId, true);
        }
        $_downId = $this->ioGet('_downId');
        if ($_downId) {
            $this->ResetIdx($_downId, false);
        }
        $sort = $this->_doOrderBy($conditions);
        $items = $this->findAll($conditions, $sort, $fields);
        if ($format) {
            foreach ($items as &$item) {
                $this->setJoinRow($item);
                $this->_doDric($item);
                $this->FormatRow($item);
                if (isset($item['createTime'])) $item['createTime_str'] = Time2Str($item['createTime']);
            }
        }
        return $items;
    }

    function _doOrderBy(&$arr)
    {
        if (isset($arr['multiSort']) && is_array($arr['multiSort'])) {
            $sortStr = "";
            foreach ($arr['multiSort'] as $k => $v) {
                $sortStr .= ' ' . $k . ' ' . $v . ',';
            }
            $sortStr = rtrim($sortStr, ',');
            return $sortStr;
        }
        if (isset($arr['basesort'])) {
            $orArr = explode('|', $arr['basesort']);
            return "{$orArr[0]} {$orArr[1]}";
        }
        if (isset($arr['sort'])) {
            $sort = isset($arr['sort']) ? $arr['sort'] : $this->key;
            $dir = isset($arr['dir']) ? $arr['dir'] : 'desc';
            return "{$sort} {$dir}";
        }
        return ($this->HasColumn('idx') ? 'idx asc' : $this->key . ' desc');
    }

    /**
     * 重新调整顺序
     * @param $aimId
     * @param bool $isUp
     * @return bool
     */
    function ResetIdx($aimId, $isUp = true)
    {
        if (!$this->HasColumn('idx')) {
            return false;
        }
        $row = $this->getOne($aimId);
        if (!$row)
            return false;

        $cond = $this->getIdxCond($row);

        $items = $this->findAll($cond, 'idx asc', 'id,idx');
        if (!$items)
            return true;
        //第一个了还要升
        if ($isUp && $items[0][$this->key] == $aimId)
            return true;
        //最后一个还要降
        if ($isUp == false && $items[count($items) - 1][$this->key] == $aimId)
            return true;

        for ($i = 0; $i < count($items); $i++) {
            $idx = $i + 1;
            $row = $items[$i];
            if ($aimId != $row[$this->key]) {
                //顺序不对给他调整
                if ($row['idx'] != $idx) {
                    $this->UpdateIm(array($this->key => $row[$this->key], 'idx' => $idx));
                }
                continue;
            }
            if ($isUp) { //升
                $this->UpdateIm(array($this->key => $row[$this->key], 'idx' => $idx - 1));
                $this->UpdateIm(array($this->key => $items[$i - 1][$this->key], 'idx' => $idx));
            } else {//降
                $this->UpdateIm(array($this->key => $row[$this->key], 'idx' => $idx + 1));
                $this->UpdateIm(array($this->key => $items[$i + 1][$this->key], 'idx' => $idx));
                $i++;
            }
        }
    }

    function getOne($idOrArr, $format = false, $fields = '*')
    {
        if (is_array($idOrArr)) {
            $conditions = $idOrArr;
        } else {
            $conditions = [$this->key => $idOrArr];
        }
        $items = $this->find($conditions, null, $fields);
        if ($format) {
            $this->setJoinRow($items);
            $this->_doDric($item);
            if ($items['createTime']) $items['createTime_str'] = Time2Str($items['createTime']);
            $this->FormatRow($items);
        }
        return $items;
    }

    //链接表查询的时候 需要做本类下面的缓存
    function getOneX($col, $val = '')
    {
        global $get_one_cache;
        //依旧采用原来的查询方式
        if (is_array($col)) {
            $cond = $col;
        } else {
            if ($val == '' || $val == null) return array();
            $cond = array($col => $val);
        }
        $key = md5(json_encode($cond));
        if (isset($get_one_cache[$key])) {
            return $get_one_cache[$key];
        }
        $get_one_cache[$key] = $this->getOne($cond);
        return $get_one_cache[$key];
    }

    function getMap($keyCol = 'id', $conditions = array(), $valCol = '*')
    {
        $items = $this->findAll($conditions, null, $valCol);
        $map = array();
        $cols = explode(',', $keyCol);
        foreach ($items as $row) {
            $key = '';
            foreach ($cols as $col) {
                $key .= $row[$col];
            }
            $map[$key] = $row;
        }
        return $map;
    }

    /**
     * name:根据条件，获取制定字段名称
     * memo:用于根据id获取指定字段值
     *      1.如果是数字或者字符串，将$id作为主键查询，支持id号，逗号分开
     *      2.如果是数组，直接执行setwhere 组织查询条件
     * @param $id 查询条件，有2种情况:
     * @param string $col
     * @return string
     */
    function getName($id, $col = 'name')
    {
        //字段的设置
        if (empty($id)) {
            return '';
        }
        $ret = array();
        if (is_array($id)) {
            $items = $this->getList($id, false, $col);
            foreach ($items as $row) {
                $ret[] = $row[$col];
            }
        } else {
            $arr = explode(',', $id);
            foreach ($arr as $k => $v) {
                if ($row = $this->getOne($v, false)) {
                    $ret[] = $row[$col];
                }
            }
        }
        return implode(',', $ret);
    }

    private function HasColumn($col)
    {
        if (!isset($this->clos)) {
            $this->GetCols();
        }
        return in_array($col, $this->clos) ? true : false;
    }

    private function GetCols()
    {
        $arr = $this->query("SHOW FULL FIELDS FROM {$this->table};");
        for ($n = 0; $n < count($arr); $n++) {
            $this->clos[] = $arr[$n]['Field'];
        }
    }
}

class View
{
    private $left_delimiter, $right_delimiter, $template_dir, $compile_dir;
    private $template_vals = array();

    function __construct($template_dir, $compile_dir, $left_delimiter = '<{', $right_delimiter = '}>')
    {
        $this->left_delimiter = $left_delimiter;
        $this->right_delimiter = $right_delimiter;
        $this->template_dir = $template_dir;
        $this->compile_dir = $compile_dir;
    }

    function render($tempalte_name)
    {
        $complied_file = $this->compile($tempalte_name);

        @ob_start();
        extract($this->template_vals, EXTR_SKIP);
        $_view_obj = &$this;
        include $complied_file;

        return ob_get_clean();
    }

    function assign($mixed, $val = '')
    {
        if (is_array($mixed)) {
            foreach ($mixed as $k => $v) {
                if ($k != '') $this->template_vals[$k] = $v;
            }
        } else {
            if ($mixed != '') $this->template_vals[$mixed] = $val;
        }
    }

    function compile($tempalte_name)
    {
        $file = $this->template_dir . DS . $tempalte_name;
        if (!file_exists($file)) err('Err: "' . $file . '" is not exists!');
        if (!is_writable($this->compile_dir) || !is_readable($this->compile_dir)) err('Err: Directory "' . $this->compile_dir . '" is not writable or readable');

        $complied_file = $this->compile_dir . DS . md5(realpath($file)) . '.' . filemtime($file) . '.' . basename($tempalte_name) . '.php';
        if (file_exists($complied_file)) return $complied_file;

        $template_data = file_get_contents($file);
        $template_data = $this->_compile_struct($template_data);
        $template_data = $this->_compile_function($template_data);
        $template_data = '<?php if(!class_exists("View", false)) exit("no direct access allowed");?>' . $template_data;

        $this->_clear_compliedfile($tempalte_name);
        $tmp_file = $complied_file . uniqid('_tpl', true);
        if (!file_put_contents($tmp_file, $template_data)) err('Err: File "' . $tmp_file . '" can not be generated.');

        $success = @rename($tmp_file, $complied_file);
        if (!$success) {
            if (is_file($complied_file)) @unlink($complied_file);
            $success = @rename($tmp_file, $complied_file);
        }
        if (!$success) err('Err: File "' . $complied_file . '" can not be generated.');
        return $complied_file;
    }

    private function _compile_struct($template_data)
    {
        $foreach_inner_before = '<?php if(!empty($1)){ $_foreach_$3_counter = 0; $_foreach_$3_total = count($1);?>';
        $foreach_inner_after = '<?php $_foreach_$3_index = $_foreach_$3_counter;$_foreach_$3_iteration = $_foreach_$3_counter + 1;$_foreach_$3_first = ($_foreach_$3_counter == 0);$_foreach_$3_last = ($_foreach_$3_counter == $_foreach_$3_total - 1);$_foreach_$3_counter++;?>';
        $pattern_map = array(
            '<{\*([\s\S]+?)\*}>' => '<?php /* $1*/?>',
            '<{#(.*?)}>' => '<?php echo $1; ?>',
            '(<{((?!}>).)*?)(\$[\w\"\'\[\]]+?)\.(\w+)(.*?}>)' => '$1$3[\'$4\']$5',
            '(<{.*?)(\$(\w+)@(index|iteration|first|last|total))+(.*?}>)' => '$1$_foreach_$3_$4$5',
            '<{(\$[\$\w\.\"\'\[\]]+?)\snofilter\s*}>' => '<?php echo $1; ?>',
            '<{(\$[\$\w\"\'\[\]]+?)\s*=(.*?)\s*}>' => '<?php $1 =$2; ?>',
            '<{(\$[\$\w\.\"\'\[\]]+?)\s*}>' => '<?php echo htmlspecialchars($1, ENT_QUOTES, "UTF-8"); ?>',
            '<{if\s*(.+?)}>' => '<?php if ($1) : ?>',
            '<{else\s*if\s*(.+?)}>' => '<?php elseif ($1) : ?>',
            '<{else}>' => '<?php else : ?>',
            '<{break}>' => '<?php break; ?>',
            '<{continue}>' => '<?php continue; ?>',
            '<{\/if}>' => '<?php endif; ?>',
            '<{foreach\s*(\$[\$\w\.\"\'\[\]]+?)\s*as(\s*)\$([\w\"\'\[\]]+?)}>' => $foreach_inner_before . '<?php foreach( $1 as $$3 ) : ?>' . $foreach_inner_after,
            '<{foreach\s*(\$[\$\w\.\"\'\[\]]+?)\s*as\s*(\$[\w\"\'\[\]]+?)\s*=>\s*\$([\w\"\'\[\]]+?)}>' => $foreach_inner_before . '<?php foreach( $1 as $2 => $$3 ) : ?>' . $foreach_inner_after,
            '<{\/foreach}>' => '<?php endforeach; }?>',
            '<{include\s*file=(.+?)}>' => '<?php include $_view_obj->compile($1); ?>',
        );
        $pattern = $replacement = array();
        foreach ($pattern_map as $p => $r) {
            $pattern = '/' . str_replace(array("<{", "}>"), array($this->left_delimiter . '\s*', '\s*' . $this->right_delimiter), $p) . '/i';
            $count = 1;
            while ($count != 0) {
                $template_data = preg_replace($pattern, $r, $template_data, -1, $count);
            }
        }
        return $template_data;
    }

    private function _compile_function($template_data)
    {
        $pattern = '/' . $this->left_delimiter . '(\w+)\s*(.*?)' . $this->right_delimiter . '/';
        return preg_replace_callback($pattern, array($this, '_compile_function_callback'), $template_data);
    }

    private function _compile_function_callback($matches)
    {
        if (empty($matches[2])) return '<?php echo ' . $matches[1] . '();?>';
        $sysfunc = preg_replace('/\((.*)\)\s*$/', '<?php echo ' . $matches[1] . '($1);?>', $matches[2], -1, $count);
        if ($count) return $sysfunc;

        $pattern_inner = '/\b([-\w]+?)\s*=\s*(\$[\w"\'\]\[\-_>\$]+|"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|([->\w]+))\s*?/';
        $params = "";
        if (preg_match_all($pattern_inner, $matches[2], $matches_inner, PREG_SET_ORDER)) {
            $params = "array(";
            foreach ($matches_inner as $m) $params .= '\'' . $m[1] . "'=>" . $m[2] . ", ";
            $params .= ")";
        } else {
            err('Err: Parameters of \'' . $matches[1] . '\' is incorrect!');
        }
        return '<?php echo ' . $matches[1] . '(' . $params . ');?>';
    }

    private function _clear_compliedfile($tempalte_name)
    {
        $dir = scandir($this->compile_dir);
        if ($dir) {
            $part = md5(realpath($this->template_dir . DS . $tempalte_name));
            foreach ($dir as $d) {
                if (substr($d, 0, strlen($part)) == $part) {
                    @unlink($this->compile_dir . DS . $d);
                }
            }
        }
    }
}